(import
  (except (rnrs base) let-values map error)
  (only (guile) lambda* λ command-line string-null?)
  (srfi srfi-1)
  (srfi srfi-69)
  (ice-9 peg)
  (fileio)
  (grammar)
  (logic-utils)
  (display-utils)
  (set)
  (prefix (peg-tree-utils) peg-tree:))

;; ================
;; DATA ABSTRACTION
;; ================

;; rule

(define rule-get-container
  (λ (rule)
    (peg-tree:find-in-tree rule 'CONTAINER symbol=?)))


(define rule-get-contained-bags
  (λ (rule)
    (map get-rule-key-and-count
         (peg-tree:find-in-tree*
          rule
          (λ (sym) (symbol=? sym 'CONTAINMENT-POSSIBILITY))))))

;; bags

(define get-count
  (λ (container)
    (peg-tree:find-in-tree container 'COUNT symbol=?)))


(define get-color-mod
  (λ (container)
    (peg-tree:find-in-tree container 'COLOR-MOD symbol=?)))


(define get-color
  (λ (container)
    (peg-tree:find-in-tree container 'COLOR symbol=?)))

;; containments

(define container-or-containment->rule-key
  (λ (container-or-containment)
    ;; (simple-format (current-output-port) "making rule-key for container-or-containment: ~a\n" container-or-containment)
    (let ([color-mod (get-color-mod container-or-containment)]
          [color (get-color container-or-containment)])
      (cond
       [(and color-mod color)
        (cons (first (get-color-mod container-or-containment))
              (first (get-color container-or-containment)))]
       [else
        (cons #f #f)]))))


(define get-rule-key-and-count
  (λ (containment)
    (let ([count (string->number (first (get-count containment)))]
          [color-mod (first (get-color-mod containment))]
          [color (first (get-color containment))])
      (cond
       [(and color-mod color count)
        (cons (cons color-mod color) count)]
       [else
        (cons (cons #f #f) #f)]))))


(define get-contained-bag-spec-key
  (λ (contained-bag-spec)
    (car contained-bag-spec)))


(define get-contained-bag-spec-count
  (λ (contained-bag-spec)
    (cdr contained-bag-spec)))

;; ==========
;; MAIN LOGIC
;; ==========

(define count-bags
  (λ (bag-mapping key)
    "count the bags contained in the bag specified by KEY"
    (apply +
           (map (λ (contained-bag-spec)
                  (let ([count
                         (get-contained-bag-spec-count contained-bag-spec)]
                        [next-key
                         (get-contained-bag-spec-key contained-bag-spec)])
                    (+ count (* count (count-bags bag-mapping next-key)))))
                (hash-table-ref bag-mapping key)))))


(define main
  (λ (cmd-line-args)
    (let* ([lines (get-lines-from-file (second cmd-line-args))]
           [parsed-lines (map (λ (line) (match-pattern RULE line)) lines)]
           [rules (map peg:tree parsed-lines)]
           [bag-mapping (make-hash-table equal?)])
      ;; create a mapping for efficiently accessing the
      ;; rules by a key consisting of color mod and color.
      (let next ([remaining-rules rules])
        (cond
         [(null? remaining-rules) 'done]
         [else
          (let* ([rule (first remaining-rules)]
                 [container (rule-get-container rule)]
                 [key (container-or-containment->rule-key container)]
                 [val (rule-get-contained-bags rule)])
            (hash-table-set! bag-mapping key val)
            (next (cdr remaining-rules)))]))
      ;; check for each rule recursively, whether it
      ;; contains and shiny golden bags at arbitrary depth
      ;; and if so, count it
      (count-bags bag-mapping (cons "shiny" "gold")))))


;; (display-list (main (command-line)))
(simple-format (current-output-port) "~a\n" (main (command-line)))
